/*
 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.presentation.rmi;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

import java.lang.reflect.Method;

import java.rmi.Remote;

import javax.rmi.CORBA.Tie;

import com.sun.corba.se.spi.orbutil.proxy.InvocationHandlerFactory;

import com.sun.corba.se.spi.presentation.rmi.IDLNameTranslator;
import com.sun.corba.se.spi.presentation.rmi.DynamicMethodMarshaller;
import com.sun.corba.se.spi.presentation.rmi.PresentationManager;

import com.sun.corba.se.spi.logging.CORBALogDomains;

import com.sun.corba.se.impl.logging.ORBUtilSystemException;

import com.sun.corba.se.impl.presentation.rmi.IDLNameTranslatorImpl;
import com.sun.corba.se.impl.presentation.rmi.StubFactoryProxyImpl;

import com.sun.corba.se.impl.orbutil.graph.Node;
import com.sun.corba.se.impl.orbutil.graph.Graph;
import com.sun.corba.se.impl.orbutil.graph.GraphImpl;

public final class PresentationManagerImpl implements PresentationManager {

  private Map classToClassData;
  private Map methodToDMM;
  private PresentationManager.StubFactoryFactory staticStubFactoryFactory;
  private PresentationManager.StubFactoryFactory dynamicStubFactoryFactory;
  private ORBUtilSystemException wrapper = null;
  private boolean useDynamicStubs;

  public PresentationManagerImpl(boolean useDynamicStubs) {
    this.useDynamicStubs = useDynamicStubs;
    wrapper = ORBUtilSystemException.get(
        CORBALogDomains.RPC_PRESENTATION);

    // XXX these should probably be WeakHashMaps.
    classToClassData = new HashMap();
    methodToDMM = new HashMap();
  }

////////////////////////////////////////////////////////////////////////////////
// PresentationManager interface
////////////////////////////////////////////////////////////////////////////////

  public synchronized DynamicMethodMarshaller getDynamicMethodMarshaller(
      Method method) {
    if (method == null) {
      return null;
    }

    DynamicMethodMarshaller result =
        (DynamicMethodMarshaller) methodToDMM.get(method);
    if (result == null) {
      result = new DynamicMethodMarshallerImpl(method);
      methodToDMM.put(method, result);
    }

    return result;
  }

  public synchronized ClassData getClassData(Class cls) {
    ClassData result = (ClassData) classToClassData.get(cls);
    if (result == null) {
      result = new ClassDataImpl(cls);
      classToClassData.put(cls, result);
    }

    return result;
  }

  private class ClassDataImpl implements PresentationManager.ClassData {

    private Class cls;
    private IDLNameTranslator nameTranslator;
    private String[] typeIds;
    private PresentationManager.StubFactory sfactory;
    private InvocationHandlerFactory ihfactory;
    private Map dictionary;

    public ClassDataImpl(Class cls) {
      this.cls = cls;
      Graph gr = new GraphImpl();
      NodeImpl root = new NodeImpl(cls);
      Set rootSet = getRootSet(cls, root, gr);

      // At this point, rootSet contains those remote interfaces
      // that are not related by inheritance, and gr contains
      // all reachable remote interfaces.

      Class[] interfaces = getInterfaces(rootSet);
      nameTranslator = IDLNameTranslatorImpl.get(interfaces);
      typeIds = makeTypeIds(root, gr, rootSet);
      ihfactory = new InvocationHandlerFactoryImpl(
          PresentationManagerImpl.this, this);
      dictionary = new HashMap();
    }

    public Class getMyClass() {
      return cls;
    }

    public IDLNameTranslator getIDLNameTranslator() {
      return nameTranslator;
    }

    public String[] getTypeIds() {
      return typeIds;
    }

    public InvocationHandlerFactory getInvocationHandlerFactory() {
      return ihfactory;
    }

    public Map getDictionary() {
      return dictionary;
    }
  }

  public PresentationManager.StubFactoryFactory getStubFactoryFactory(
      boolean isDynamic) {
    if (isDynamic) {
      return dynamicStubFactoryFactory;
    } else {
      return staticStubFactoryFactory;
    }
  }

  public void setStubFactoryFactory(boolean isDynamic,
      PresentationManager.StubFactoryFactory sff) {
    if (isDynamic) {
      dynamicStubFactoryFactory = sff;
    } else {
      staticStubFactoryFactory = sff;
    }
  }

  public Tie getTie() {
    return dynamicStubFactoryFactory.getTie(null);
  }

  public boolean useDynamicStubs() {
    return useDynamicStubs;
  }

////////////////////////////////////////////////////////////////////////////////
// Graph computations
////////////////////////////////////////////////////////////////////////////////

  private Set getRootSet(Class target, NodeImpl root, Graph gr) {
    Set rootSet = null;

    if (target.isInterface()) {
      gr.add(root);
      rootSet = gr.getRoots(); // rootSet just contains root here
    } else {
      // Use this class and its superclasses (not Object) as initial roots
      Class superclass = target;
      Set initialRootSet = new HashSet();
      while ((superclass != null) && !superclass.equals(Object.class)) {
        Node node = new NodeImpl(superclass);
        gr.add(node);
        initialRootSet.add(node);
        superclass = superclass.getSuperclass();
      }

      // Expand all nodes into the graph
      gr.getRoots();

      // remove the roots and find roots again
      gr.removeAll(initialRootSet);
      rootSet = gr.getRoots();
    }

    return rootSet;
  }

  private Class[] getInterfaces(Set roots) {
    Class[] classes = new Class[roots.size()];
    Iterator iter = roots.iterator();
    int ctr = 0;
    while (iter.hasNext()) {
      NodeImpl node = (NodeImpl) iter.next();
      classes[ctr++] = node.getInterface();
    }

    return classes;
  }

  private String[] makeTypeIds(NodeImpl root, Graph gr, Set rootSet) {
    Set nonRootSet = new HashSet(gr);
    nonRootSet.removeAll(rootSet);

    // List<String> for the typeids
    List result = new ArrayList();

    if (rootSet.size() > 1) {
      // If the rootSet has more than one element, we must
      // put the type id of the implementation class first.
      // Root represents the implementation class here.
      result.add(root.getTypeId());
    }

    addNodes(result, rootSet);
    addNodes(result, nonRootSet);

    return (String[]) result.toArray(new String[result.size()]);
  }

  private void addNodes(List resultList, Set nodeSet) {
    Iterator iter = nodeSet.iterator();
    while (iter.hasNext()) {
      NodeImpl node = (NodeImpl) iter.next();
      String typeId = node.getTypeId();
      resultList.add(typeId);
    }
  }

  private static class NodeImpl implements Node {

    private Class interf;

    public Class getInterface() {
      return interf;
    }

    public NodeImpl(Class interf) {
      this.interf = interf;
    }

    public String getTypeId() {
      return "RMI:" + interf.getName() + ":0000000000000000";
    }

    public Set getChildren() {
      Set result = new HashSet();
      Class[] interfaces = interf.getInterfaces();
      for (int ctr = 0; ctr < interfaces.length; ctr++) {
        Class cls = interfaces[ctr];
        if (Remote.class.isAssignableFrom(cls) &&
            !Remote.class.equals(cls)) {
          result.add(new NodeImpl(cls));
        }
      }

      return result;
    }

    public String toString() {
      return "NodeImpl[" + interf + "]";
    }

    public int hashCode() {
      return interf.hashCode();
    }

    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }

      if (!(obj instanceof NodeImpl)) {
        return false;
      }

      NodeImpl other = (NodeImpl) obj;

      return other.interf.equals(interf);
    }
  }
}
